#include <vector>
#include <cmath>
#include "tgaimage.h"
#include "model.h"
#include "geometry.h"

const TGAColor white = TGAColor(255, 255, 255, 255);
const TGAColor red = TGAColor(255, 0, 0, 255);
const TGAColor green = TGAColor(0, 255, 0, 255);
const TGAColor blue = TGAColor(0, 0, 255, 255);

void line(int x0, int y0, int x1, int y1, TGAImage &image, TGAColor color) {

//    p = p0 + (p1 - p0) * t
//    for (float t = 0; t <= 1 ; t+=.01) {
//        int x = x0 + (x1 - x0) * t;
//        int y = y0 + (y1 - y0) * t;
//        image.set(x, y, white);
//    }

//    for (int x = x0; x <= x1; x++)
//    {
//        float t = (x - x0) / (float)(x1 - x0);
//        int y = y0 + (y1 - y0) * t;
//        image.set(x, y, white);
//    }
    bool steep = false;
    if (std::abs(x1 - x0) < std::abs(y1 - y0)) {
        std::swap(x0, y0);
        std::swap(x1, y1);
        steep = true;
    }

    if (x0 > x1) {
        std::swap(x0, x1);
        std::swap(y0, y1);
    }

//    for (int x = x0; x <= x1; x++)
//    {
//        float t = (x - x0)/(float )(x1 - x0);
//        int y = y0 + (y1 - y0) * t;
//        if (steep)
//        {
//            image.set(y, x, color);
//        }else
//        {
//            image.set(x, y, color);
//        }
//    }

    int dx = x1 - x0;
    int dy = y1 - y0;
    int derror = std::abs(dy) * 2;
    int error = 0;
    int y = y0;
    for (int x = x0; x <= x1; x++) {
        if (steep) {
            image.set(y, x, color);
        } else {
            image.set(x, y, color);
        }
        error += derror;
        if (error > dx) {
            y += (y1 > y0 ? 1 : -1);
            error -= dx * 2;
        }
    }
}

void line(Vec2i p0, Vec2i p1, TGAImage &image, TGAColor color) {
    line(p0.x, p0.y, p1.x, p1.y, image, color);
}

void triangle(Vec2i t0, Vec2i t1, Vec2i t2, TGAImage &image, TGAColor color)
{
    if (t0.y>t1.y) std::swap(t0, t1);
    if (t0.y>t2.y) std::swap(t0, t2);
    if (t1.y>t2.y) std::swap(t1, t2);
    int total_height = t2.y-t0.y;
    for (int y=t0.y; y<=t1.y; y++) {
        int segment_height = t1.y-t0.y+1;
        float alpha = (float)(y-t0.y)/total_height;
        float beta  = (float)(y-t0.y)/segment_height; // be careful with divisions by zero
        Vec2i A = t0 + (t2-t0)*alpha;
        Vec2i B = t0 + (t1-t0)*beta;
        if (A.x>B.x) std::swap(A, B);
        for (int j=A.x; j<=B.x; j++) {
            image.set(j, y, color); // attention, due to int casts t0.y+i != A.y
        }
    }
    for (int y=t1.y; y<=t2.y; y++) {
        int segment_height =  t2.y-t1.y+1;
        float alpha = (float)(y-t0.y)/total_height;
        float beta  = (float)(y-t1.y)/segment_height; // be careful with divisions by zero
        Vec2i A = t0 + (t2-t0)*alpha;
        Vec2i B = t1 + (t2-t1)*beta;
        if (A.x>B.x) std::swap(A, B);
        for (int j=A.x; j<=B.x; j++) {
            image.set(j, y, color); // attention, due to int casts t0.y+i != A.y
        }
    }
}

int getx(Vec2i &p0, Vec2i &p1, int y)
{
    int x0 = p0.x;
    int x1 = p1.x;
    int y0 = p0.y;
    int y1 = p1.y;

    float t = (y - y0) / (float)(y1 - y0);
    return x0 * (1-t) + x1* t;
}

void fillTopTriangle(Vec2i leftBottomT, Vec2i rightBottomT, Vec2i topT, TGAImage &image, TGAColor color)
{
    for (int y = leftBottomT.y; y <= topT.y; y++)
    {
        int leftX = getx(leftBottomT, topT, y);
        int rightX = getx(rightBottomT, topT, y);
        for (int x = leftX; x <= rightX; x++)
        {
            image.set(x, y, color);
        }
    }
}

void fillBottomTriangle(Vec2i leftBottomT, Vec2i rightBottomT, Vec2i bottomT, TGAImage &image, TGAColor color)
{
    for (int y = bottomT.y; y <= leftBottomT.y; y++)
    {
        int leftX = getx(leftBottomT, bottomT, y);
        int rightX = getx(rightBottomT, bottomT, y);
        for (int x = leftX; x <= rightX; x++)
        {
            image.set(x, y, color);
        }
    }
}

void fillTriangle(Vec2i t0, Vec2i t1, Vec2i t2, TGAImage &image, TGAColor color)
{
    if (t0.y < t1.y)
    {
        std::swap(t0, t1);
    }

    if (t0.y < t2.y)
    {
        std::swap(t0, t2);
    }

    if (t1.y < t2.y)
    {
        std::swap(t1, t2);
    }

    Vec2i t3 = Vec2i (getx(t0, t2, t1.y), t1.y);
    if (t1.x < t3.x)
    {
        fillTopTriangle(t1, t3, t0,image, color);
        fillBottomTriangle(t1, t3, t2, image, color);
    }else
    {
        fillTopTriangle(t3, t1, t0,image, color);
        fillBottomTriangle(t3, t1, t2, image, color);
    }
}

void Lesson1BresenhamLineDrawing()
{
    const int width = 800;
    const int height = 800;
    Model *model = new Model("obj/african_head/african_head.obj");
    TGAImage image(width, height, TGAImage::RGB);

    Vec3f light_dir(0,0,-1);
    for (int i=0; i<model->nfaces(); i++) {
        std::vector<int> face = model->face(i);
        Vec2i screen_coords[3];
        Vec3f world_coords[3];
        for (int j=0; j<3; j++) {
            Vec3f v = model->vert(face[j]);
            screen_coords[j] = Vec2i((v.x+1.)*width/2., (v.y+1.)*height/2.);
            world_coords[j]  = v;
        }
        Vec3f n = (world_coords[2]-world_coords[0])^(world_coords[1]-world_coords[0]);
        n.normalize();
        float intensity = n*light_dir;
        if (intensity>0) {
            triangle(screen_coords[0], screen_coords[1], screen_coords[2], image, TGAColor(intensity*255, intensity*255, intensity*255, 255));
        }
    }

    image.flip_vertically();
    image.write_tga_file("lesson1.tga");

    delete model;
}

void Lesson2Trianglerasterization()
{
    TGAImage image(200, 200, TGAImage::RGB);

    Vec2i t0[3] = {Vec2i(10, 70),   Vec2i(50, 160),  Vec2i(70, 80)};
    Vec2i t1[3] = {Vec2i(180, 50),  Vec2i(150, 1),   Vec2i(70, 180)};
    Vec2i t2[3] = {Vec2i(180, 150), Vec2i(120, 160), Vec2i(130, 180)};
    triangle(t0[0], t0[1], t0[2], image, red);
    triangle(t1[0], t1[1], t1[2], image, white);
    triangle(t2[0], t2[1], t2[2], image, green);

//    Vec2i t0[3] = {Vec2i(150, 0),   Vec2i(200, 0),  Vec2i(100, 200)};
//    fillTopTriangle(t0[0], t0[1], t0[2], image, red);

//    Vec2i t1[3] = {Vec2i(0, 200),   Vec2i(200, 200),  Vec2i(100, 0)};
//    fillBottomTriangle(t1[0], t1[1], t1[2], image, red);

    image.flip_vertically();
    image.write_tga_file("lesson2.tga");
}

int main() {
    Lesson2Trianglerasterization();
    Lesson1BresenhamLineDrawing();
    return 0;
}
